1   /*
2    * Copyright (c) 1997, 2011, Oracle and/or its affiliates. All rights reserved.
3    *
4    * Redistribution and use in source and binary forms, with or without
5    * modification, are permitted provided that the following conditions
6    * are met:
7    *
8    *   - Redistributions of source code must retain the above copyright
9    *     notice, this list of conditions and the following disclaimer.
10   *
11   *   - Redistributions in binary form must reproduce the above copyright
12   *     notice, this list of conditions and the following disclaimer in the
13   *     documentation and/or other materials provided with the distribution.
14   *
15   *   - Neither the name of Oracle nor the names of its
16   *     contributors may be used to endorse or promote products derived
17   *     from this software without specific prior written permission.
18   *
19   * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
20   * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
21   * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
22   * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER OR
23   * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
24   * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
25   * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
26   * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
27   * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
28   * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
29   * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30   */
31  
32  
33  import java.applet.Applet;
34  import java.awt.AWTEvent;
35  import java.awt.BorderLayout;
36  import java.awt.Button;
37  import java.awt.Canvas;
38  import java.awt.Choice;
39  import java.awt.Color;
40  import java.awt.Dimension;
41  import java.awt.FlowLayout;
42  import java.awt.FontMetrics;
43  import java.awt.Frame;
44  import java.awt.Graphics;
45  import java.awt.Image;
46  import java.awt.Label;
47  import java.awt.LayoutManager;
48  import java.awt.Panel;
49  import java.awt.TextField;
50  import java.awt.Toolkit;
51  import java.awt.event.ActionEvent;
52  import java.awt.event.ActionListener;
53  import java.awt.event.KeyEvent;
54  import java.awt.event.TextEvent;
55  import java.awt.image.ColorModel;
56  import java.awt.image.MemoryImageSource;
57  
58  
59  enum DitherMethod {
60  
61      NOOP, RED, GREEN, BLUE, ALPHA, SATURATION
62  };
63  
64  
65  @SuppressWarnings("serial")
66  public class DitherTest extends Applet implements Runnable {
67  
68      private Thread runner;
69      private DitherControls XControls;
70      private DitherControls YControls;
71      private DitherCanvas canvas;
72  
73      public static void main(String args[]) {
74          Frame f = new Frame("DitherTest");
75          DitherTest ditherTest = new DitherTest();
76          ditherTest.init();
77          f.add("Center", ditherTest);
78          f.pack();
79          f.setVisible(true);
80          ditherTest.start();
81      }
82  
83      @Override
84      public void init() {
85          String xspec = null, yspec = null;
86          int xvals[] = new int[2];
87          int yvals[] = new int[2];
88  
89          try {
90              xspec = getParameter("xaxis");
91              yspec = getParameter("yaxis");
92          } catch (NullPointerException ignored) {
93              //only occurs if run as application
94          }
95  
96          if (xspec == null) {
97              xspec = "red";
98          }
99          if (yspec == null) {
100             yspec = "blue";
101         }
102         DitherMethod xmethod = colorMethod(xspec, xvals);
103         DitherMethod ymethod = colorMethod(yspec, yvals);
104 
105         setLayout(new BorderLayout());
106         XControls = new DitherControls(this, xvals[0], xvals[1],
107                 xmethod, false);
108         YControls = new DitherControls(this, yvals[0], yvals[1],
109                 ymethod, true);
110         YControls.addRenderButton();
111         add("North", XControls);
112         add("South", YControls);
113         add("Center", canvas = new DitherCanvas());
114     }
115 
116     private DitherMethod colorMethod(String s, int vals[]) {
117         DitherMethod method = DitherMethod.NOOP;
118         if (s == null) {
119             s = "";
120         }
121         String lower = s.toLowerCase();
122 
123         for (DitherMethod m : DitherMethod.values()) {
124             if (lower.startsWith(m.toString().toLowerCase())) {
125                 method = m;
126                 lower = lower.substring(m.toString().length());
127             }
128         }
129         if (method == DitherMethod.NOOP) {
130             vals[0] = 0;
131             vals[1] = 0;
132             return method;
133         }
134         int begval = 0;
135         int endval = 255;
136         try {
137             int dash = lower.indexOf('-');
138             if (dash < 0) {
139                 endval = Integer.parseInt(lower);
140             } else {
141                 begval = Integer.parseInt(lower.substring(0, dash));
142                 endval = Integer.parseInt(lower.substring(dash + 1));
143             }
144         } catch (NumberFormatException ignored) {
145         }
146 
147         if (begval < 0) {
148             begval = 0;
149         } else if (begval > 255) {
150             begval = 255;
151         }
152 
153         if (endval < 0) {
154             endval = 0;
155         } else if (endval > 255) {
156             endval = 255;
157         }
158 
159         vals[0] = begval;
160         vals[1] = endval;
161         return method;
162     }
163 
164     /**
165      * Calculates and returns the image.  Halts the calculation and returns
166      * null if the Applet is stopped during the calculation.
167      */
168     private Image calculateImage() {
169         Thread me = Thread.currentThread();
170 
171         int width = canvas.getSize().width;
172         int height = canvas.getSize().height;
173         int xvals[] = new int[2];
174         int yvals[] = new int[2];
175         int xmethod = XControls.getParams(xvals);
176         int ymethod = YControls.getParams(yvals);
177         int pixels[] = new int[width * height];
178         int c[] = new int[4];   //temporarily holds R,G,B,A information
179         int index = 0;
180         for (int j = 0; j < height; j++) {
181             for (int i = 0; i < width; i++) {
182                 c[0] = c[1] = c[2] = 0;
183                 c[3] = 255;
184                 if (xmethod < ymethod) {
185                     applyMethod(c, xmethod, i, width, xvals);
186                     applyMethod(c, ymethod, j, height, yvals);
187                 } else {
188                     applyMethod(c, ymethod, j, height, yvals);
189                     applyMethod(c, xmethod, i, width, xvals);
190                 }
191                 pixels[index++] = ((c[3] << 24) | (c[0] << 16) | (c[1] << 8)
192                         | c[2]);
193             }
194 
195             // Poll once per row to see if we've been told to stop.
196             if (runner != me) {
197                 return null;
198             }
199         }
200         return createImage(new MemoryImageSource(width, height,
201                 ColorModel.getRGBdefault(), pixels, 0, width));
202     }
203 
204     private void applyMethod(int c[], int methodIndex, int step,
205             int total, int vals[]) {
206         DitherMethod method = DitherMethod.values()[methodIndex];
207         if (method == DitherMethod.NOOP) {
208             return;
209         }
210         int val = ((total < 2)
211                 ? vals[0]
212                 : vals[0] + ((vals[1] - vals[0]) * step / (total - 1)));
213         switch (method) {
214             case RED:
215                 c[0] = val;
216                 break;
217             case GREEN:
218                 c[1] = val;
219                 break;
220             case BLUE:
221                 c[2] = val;
222                 break;
223             case ALPHA:
224                 c[3] = val;
225                 break;
226             case SATURATION:
227                 int max = Math.max(Math.max(c[0], c[1]), c[2]);
228                 int min = max * (255 - val) / 255;
229                 if (c[0] == 0) {
230                     c[0] = min;
231                 }
232                 if (c[1] == 0) {
233                     c[1] = min;
234                 }
235                 if (c[2] == 0) {
236                     c[2] = min;
237                 }
238                 break;
239         }
240     }
241 
242     @Override
243     public void start() {
244         runner = new Thread(this);
245         runner.start();
246     }
247 
248     @Override
249     public void run() {
250         canvas.setImage(null);  // Wipe previous image
251         Image img = calculateImage();
252         if (img != null && runner == Thread.currentThread()) {
253             canvas.setImage(img);
254         }
255     }
256 
257     @Override
258     public void stop() {
259         runner = null;
260     }
261 
262     @Override
263     public void destroy() {
264         remove(XControls);
265         remove(YControls);
266         remove(canvas);
267     }
268 
269     @Override
270     public String getAppletInfo() {
271         return "An interactive demonstration of dithering.";
272     }
273 
274     @Override
275     public String[][] getParameterInfo() {
276         String[][] info = {
277             { "xaxis", "{RED, GREEN, BLUE, ALPHA, SATURATION}",
278                 "The color of the Y axis.  Default is RED." },
279             { "yaxis", "{RED, GREEN, BLUE, ALPHA, SATURATION}",
280                 "The color of the X axis.  Default is BLUE." }
281         };
282         return info;
283     }
284 }
285 
286 
287 @SuppressWarnings("serial")
288 class DitherCanvas extends Canvas {
289 
290     private Image img;
291     private static String calcString = "Calculating...";
292 
293     @Override
294     public void paint(Graphics g) {
295         int w = getSize().width;
296         int h = getSize().height;
297         if (img == null) {
298             super.paint(g);
299             g.setColor(Color.black);
300             FontMetrics fm = g.getFontMetrics();
301             int x = (w - fm.stringWidth(calcString)) / 2;
302             int y = h / 2;
303             g.drawString(calcString, x, y);
304         } else {
305             g.drawImage(img, 0, 0, w, h, this);
306         }
307     }
308 
309     @Override
310     public void update(Graphics g) {
311         paint(g);
312     }
313 
314     @Override
315     public Dimension getMinimumSize() {
316         return new Dimension(20, 20);
317     }
318 
319     @Override
320     public Dimension getPreferredSize() {
321         return new Dimension(200, 200);
322     }
323 
324     public Image getImage() {
325         return img;
326     }
327 
328     public void setImage(Image img) {
329         this.img = img;
330         repaint();
331     }
332 }
333 
334 
335 @SuppressWarnings("serial")
336 class DitherControls extends Panel implements ActionListener {
337 
338     private CardinalTextField start;
339     private CardinalTextField end;
340     private Button button;
341     private Choice choice;
342     private DitherTest applet;
343     private static LayoutManager dcLayout = new FlowLayout(FlowLayout.CENTER,
344             10, 5);
345 
346     public DitherControls(DitherTest app, int s, int e, DitherMethod type,
347             boolean vertical) {
348         applet = app;
349         setLayout(dcLayout);
350         add(new Label(vertical ? "Vertical" : "Horizontal"));
351         add(choice = new Choice());
352         for (DitherMethod m : DitherMethod.values()) {
353             choice.addItem(m.toString().substring(0, 1)
354                     + m.toString().substring(1).toLowerCase());
355         }
356         choice.select(type.ordinal());
357         add(start = new CardinalTextField(Integer.toString(s), 4));
358         add(end = new CardinalTextField(Integer.toString(e), 4));
359     }
360 
361     /* puts on the button */
362     public void addRenderButton() {
363         add(button = new Button("New Image"));
364         button.addActionListener(this);
365     }
366 
367     /* retrieves data from the user input fields */
368     public int getParams(int vals[]) {
369         try {
370             vals[0] = scale(Integer.parseInt(start.getText()));
371         } catch (NumberFormatException nfe) {
372             vals[0] = 0;
373         }
374         try {
375             vals[1] = scale(Integer.parseInt(end.getText()));
376         } catch (NumberFormatException nfe) {
377             vals[1] = 255;
378         }
379         return choice.getSelectedIndex();
380     }
381 
382     /* fits the number between 0 and 255 inclusive */
383     private int scale(int number) {
384         if (number < 0) {
385             number = 0;
386         } else if (number > 255) {
387             number = 255;
388         }
389         return number;
390     }
391 
392     /* called when user clicks the button */
393     @Override
394     public void actionPerformed(ActionEvent e) {
395         if (e.getSource() == button) {
396             applet.start();
397         }
398     }
399 }
400 
401 
402 @SuppressWarnings("serial")
403 class CardinalTextField extends TextField {
404 
405     String oldText = null;
406 
407     public CardinalTextField(String text, int columns) {
408         super(text, columns);
409         enableEvents(AWTEvent.KEY_EVENT_MASK | AWTEvent.TEXT_EVENT_MASK);
410         oldText = getText();
411     }
412 
413     // Consume non-digit KeyTyped events
414     // Note that processTextEvent kind of eliminates the need for this
415     // function, but this is neater, since ideally, it would prevent
416     // the text from appearing at all.  Sigh.  See bugid 4100317/4114565.
417     //
418     @Override
419     protected void processEvent(AWTEvent evt) {
420         int id = evt.getID();
421         if (id != KeyEvent.KEY_TYPED) {
422             super.processEvent(evt);
423             return;
424         }
425 
426         KeyEvent kevt = (KeyEvent) evt;
427         char c = kevt.getKeyChar();
428 
429         // Digits, backspace, and delete are okay
430         // Note that the minus sign is not allowed (neither is decimal)
431         if (Character.isDigit(c) || (c == '\b') || (c == '\u007f')) {
432             super.processEvent(evt);
433             return;
434         }
435 
436         Toolkit.getDefaultToolkit().beep();
437         kevt.consume();
438     }
439 
440     // Should consume TextEvents for non-integer Strings
441     // Store away the text in the tf for every TextEvent
442     // so we can revert to it on a TextEvent (paste, or
443     // legal key in the wrong location) with bad text
444     //
445     // Note: it would be easy to extend this to an eight-bit
446     // TextField (range 0-255), but I'll leave it as-is.
447     //
448     @Override
449     protected void processTextEvent(TextEvent te) {
450         // The empty string is okay, too
451         String newText = getText();
452         if (newText.equals("") || textIsCardinal(newText)) {
453             oldText = newText;
454             super.processTextEvent(te);
455             return;
456         }
457 
458         Toolkit.getDefaultToolkit().beep();
459         setText(oldText);
460     }
461 
462     // Returns true for Cardinal (non-negative) numbers
463     // Note that the empty string is not allowed
464     private boolean textIsCardinal(String textToCheck) {
465         try {
466             return Integer.parseInt(textToCheck, 10) >= 0;
467         } catch (NumberFormatException nfe) {
468             return false;
469         }
470     }
471 }